package com.vogue.api.common.security.config;

import cn.hutool.crypto.digest.MD5;
import com.vogue.api.common.security.granter.ImRefreshTokenGranter;
import com.vogue.api.common.security.granter.MobilePasswordTokenGranter;
import com.vogue.api.common.security.granter.MobileSmsTokenGranter;
import com.vogue.api.common.security.granter.WeChatMiniProgramTokenGranter;
import com.vogue.api.common.security.model.Oauth2User;
import com.vogue.api.common.security.service.Oauth2UserDetailsService;
import com.vogue.api.common.redisson.RedissonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.*;

/**
 * 授权服务配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    private TokenStoreType tokenStoreType = TokenStoreType.REDIS;

    //枚举 token储存方式
    private enum TokenStoreType {
        IN_MEMORY, REDIS, JWT,JDBC
    }

    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Autowired
    private Oauth2UserDetailsService userDetailsService;


    @Resource
    private DataSource dataSource;

    @Resource
    private RedissonService redissonService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //OAuth2支持的grant_type(授权方式)与功能

        //1、authorization_code -- 授权码模式(即先登录获取code,再获取token)
        //2、password -- 密码模式(将用户名,密码传过去,直接获取token)
        //3、implicit -- 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
        //4、client_credentials -- 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向'服务端'获取资源)
        //5、refresh_token -- 刷新access_token

        //"client_credentials","authorization_code","implicit",
        clients.inMemory()
                .withClient("im")
                .secret(passwordEncoder().encode("im"))
                .scopes("all")
                .authorizedGrantTypes("client_credentials","authorization_code","implicit", "refresh_token", "password", "sms", "wechat_mini");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {

        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints){

        //设置获取令牌地址
        endpoints
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .pathMapping("/oauth/token","/api/oauth/token");

        endpoints.tokenStore(tokenStore());
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        endpoints.authorizationCodeServices(authorizationCodeServices());//授权码模式
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getAuthorizationCodeServices(), endpoints.getTokenStore(), endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
        endpoints.tokenEnhancer(new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                Oauth2User user = (Oauth2User) oAuth2Authentication.getPrincipal();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("nickName", user.getNickname());
                map.put("mobile", user.getMobile());
                map.put("avatar", user.getAvatar());
                map.put("userId", user.getId());
                map.put("role", user.getRole());
                map.put("authorities", user.getAuthorities());
                token.setAdditionalInformation(map);
                token.setExpiration(new Date(System.currentTimeMillis() + 60*60*24*30));
                if (tokenStoreType == TokenStoreType.JWT) {
                    token = (DefaultOAuth2AccessToken) jwtAccessTokenConverter().enhance(oAuth2AccessToken, oAuth2Authentication);
                } else {
                    token.setValue(buildTokenValue());
                    if (token.getRefreshToken() != null) {
                        if (token.getRefreshToken() instanceof DefaultExpiringOAuth2RefreshToken) {
                            DefaultExpiringOAuth2RefreshToken refreshToken = (DefaultExpiringOAuth2RefreshToken) token.getRefreshToken();
                            token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken(buildTokenValue(), refreshToken.getExpiration()));
                        } else {
                            token.setRefreshToken(new DefaultOAuth2RefreshToken(buildTokenValue()));
                        }
                    }
                }

                //缓存 token 对 userID
                redissonService.set("IM:UserID:"+token.getValue(),user.getId()+"");
                redissonService.set("IM:UserRole:"+user.getId(),user.getRole());
                return token;
            }

            public String buildTokenValue() {
                return MD5.create().digestHex(passwordEncoder().encode(UUID.randomUUID().toString() + UUID.randomUUID().toString()));
            }

        });

    }

    private List<TokenGranter> getTokenGranters(AuthorizationCodeServices authorizationCodeServices, TokenStore tokenStore, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {

        return new ArrayList<>(Arrays.asList(
                new ImRefreshTokenGranter(tokenStore, tokenServices, clientDetailsService, requestFactory),
                new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory),
                new MobilePasswordTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory),
                new WeChatMiniProgramTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory),
                new MobileSmsTokenGranter(userDetailsService, tokenServices, clientDetailsService, requestFactory)
        ));
    }

    /**
     * 访问令牌转换器
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

        accessTokenConverter.setSigningKey("vogue-oauth2");//自定义的字符串
        ((DefaultAccessTokenConverter) accessTokenConverter.getAccessTokenConverter()).setUserTokenConverter(new DefaultUserAuthenticationConverter() {
            @Override
            public Authentication extractAuthentication(Map<String, ?> map) {
                Oauth2User oauth2User = new Oauth2User();
                BeanMap.create(oauth2User).putAll(map);
                //角色处理处理为集合使用
                Object authorities = map.get("authorities");
                if (authorities instanceof String) {
                    oauth2User.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities));
                } else if (authorities instanceof Collection) {
                    oauth2User.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection) authorities)));
                }
                return new PreAuthenticatedAuthenticationToken(oauth2User, null, oauth2User.getAuthorities());
            }
        });
        return accessTokenConverter;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Bean
    public TokenStore tokenStore() {
        switch (tokenStoreType) {
            case REDIS:
                RedisTokenStore redisTokenStore=new RedisTokenStore(connectionFactory);
                redisTokenStore.setPrefix("Oauth2:");
                return redisTokenStore;
            case JDBC:
                return new JdbcTokenStore(dataSource);
            case JWT:
                return new JwtTokenStore(jwtAccessTokenConverter());
        }
        return new InMemoryTokenStore();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
